home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Gekkan Dennou Club 147
/
Gekkan Dennou Club - 2000.8 Vol. 147 (Japan).7z
/
Gekkan Dennou Club - 2000.8 Vol. 147 (Japan) (Track 1).bin
/
docs
/
perl
/
perl20.doc
< prev
Wrap
Text File
|
2000-06-25
|
20KB
|
793 lines
おきらくPerlプログラミング入門
~~めざせ Perl マスター~~
広井 誠
最終回
○タイ(tie)
この講座も最終回となりましたが、最後は変数とクラスを結び付ける働きをす
るタイ(tie) について説明します。タイは、リファレンスやオブジェクト指向と
ともに、Perl 5 から追加された機能です。タイは、変数とユーザー定義のクラ
スを結び付ける働きをし、変数の読み出しや書き込みのタイミングで、特定のメ
ソッドを呼び出すことができます。変数には、スカラー、配列、ハッシュ、ファ
イルハンドルを指定することができます。タイを使うことで、特定の変数を監視
するといったデバッグツールの作成や、データベースとハッシュを結び付けるこ
とで、データベースへのアクセスを簡単に行うことができます。
○スカラーとタイ
まずは、スカラーから説明しましょう。変数とクラスを結び付けるには、関数
tie を使います。
tie variable, classname, list;
関数 tie は変数 variable をクラス classname に結び付けます。このように結
び付けられた変数をタイ変数といいます。tie は、クラス classname に定義さ
れている特別なメソッドを呼び出します。これは変数の種類によって異なります。
変数の種類 |呼び出されるメソッド
-----------------+--------------------
スカラー | TIESCALAR
配列 | TIEARRAY
ハッシュ | TIEHASH
ファイルハンドル | TIEHANDLE
スカラーの場合は、TIESCALAR が呼び出されます。この時、tie に与えられた
list が引数として渡されます。つまり、TIESCALAR は次のように呼び出されま
す。
classname->TIESCALAR( list );
tie から呼び出されるこれらのメソッドは、オブジェクトを返さなければいけま
せん。このオブジェクトと変数 [*1] が結び付けられます。
スカラーがタイ変数になると、変数にアクセスするたびに、次のメソッドが呼
び出されます。
アクション | 呼び出されるメソッド
-------------+--------------------------
読み出し | $obj->FETCH();
書き込み | $obj->STORE( $new_value );
廃棄 | $obj->DESTROY();
廃棄には、関数 untie によってタイを解除する、関数 undef によって変数を未
定義にする、局所変数が有効範囲から出る(スコープの終了)ことの3通りがあ
ります。変数がタイ変数となると、変数としての機能が失われる、つまり、値の
読み書きができなくなることに注意してください。値を保存したい場合は、オブ
ジェクトにインスタンス変数を用意して、メソッド FETCH で変数の値を読み出
す、STORE で変数へ値を書き込む処理が必要になります。
それでは簡単な実行例として、スカラー変数のアクセスを監視するプログラム
を作ってみましょう。最初にクラス MonScalar を定義します。
package MonScalar;
sub TIESCALAR {
my ($pkg, $name, $value) = @_;
my $obj = { name => $name, value => $value };
bless $obj, $pkg;
$obj;
}
sub FETCH {
my $obj = shift;
print 'Read : $', $obj->{'name'}, " -> $obj->{'value'}\n";
$obj->{'value'};
}
sub STORE {
my ($obj, $new_value) = @_;
print 'Write : $', $obj->{'name'}," <- $new_value\n";
$obj->{'value'} = $new_value;
}
sub DESTROY {
print "DESTORY\n";
}
TIESCALAR には引数として、変数名とその値を渡します。オブジェクトとして
無名のハッシュを生成して、変数名は name に、値は value にセットします。
後はオブジェクトをブレスして返すだけです。
FETCH は簡単です。オブジェクトから名前と値を取り出して print で出力し
ます。FETCH の返り値がその変数の値として扱われるので、値をそのまま返しま
す。STORE も簡単ですね。書き込まれる値が引数として渡されるので、それをオ
ブジェクトの value に保存するだけです。
それでは実行してみましょう。
package main;
$x = 10;
tie $x, 'MonScalar', 'x', $x;
$y = $x;
print "y = $y\n";
$x = 100;
$z = $x;
print "z = $z\n";
untie $x;
print "x = $x\n";
実行結果
Read : $x -> 10
y = 10
Write : $x <- 100
Read : $x -> 100
z = 100
DESTORY
x = 100
最初に tie を使って変数 $x とクラス MonScalar を結び付けます。tie の返
り値は、TIESCALAR で返すオブジェクトです。このオブジェクトを使って特別な
メソッド FETCH や STORE を呼び出すことができます。また、tied 関数を使っ
て、変数に結び付けられたオブジェクトを取り出すこともできます。たとえば、
$x = 100; は (tied $x)->STORE( 100 ); と同じ動作になります。
この後 $x にアクセスするたびに、MonScalar のメソッド FETCH と STORE が
呼び出されます。untie を実行すると、メソッド DESTORY が呼び出されます。
ここで変数 $x の値に注意してください。参考文献によると、「タイされた変数
について untie 関数を呼び出すと、変数の値は元に復元される」とのことです。
ところが、$x は書き換えた値 100 になっています。どうやらスカラーの場合は、
元の値には復元されないようです。配列やハッシュでは、元の値に復元されます。
Windows で動作する ActivePerl 5.005 でも、スカラーの値は復元されませんが、
配列とハッシュの値は元に戻ります。スカラーでも元の値に復元される処理系が
あるかもしれません。そこで、untie されると変数の値は復元されることを前提
に話を進めます。
[*1] 正確に説明すると、変数に格納されているデータ(スカラー、配列、ハッ
シュやファイルハンドル)とオブジェクトが結び付けられます。変数自
身と結びつくわけではないことに注意してください。
○変数の値を更新する
監視を止めると変数の値が元に戻るようでは、監視の役目を果たしているとは
いえません。かえって、ユーザーを混乱させるだけです。変数の値は、オブジェ
クトに格納されているので、監視を止める時に値を更新することにしましょう。
この処理をメソッド unmonitor で行うことにします。
[注意] X68k 版 Perl 5 では関数 tied を呼び出すことができないので(未
実装?)、これ以降のプログラムは ActivePerl 5.005 で動作チェッ
クを行いました。
# 監視を止める
sub unmonitor {
my ($pkg, $rvar) = @_;
my $obj = tied $$rvar;
my $last_value = $obj->{'value'};
untie $$rvar;
$$rvar = $last_value;
}
このメソッドは MonScalar->unmonitor( \$x ); と呼び出します。リファレンス
を使って変数を渡すことに注意してください。関数 tied でオブジェクトを取り
出し、そこに格納されている値を $last_value にセットします。その後、untie
でタイを解除してから、変数の値を $last_value に更新します。
これで正常に動作するように思いますが、実は不具合があるのです。-w オプ
ションを付けて perl を実行すると、次のメッセージが表示されます。
untie attempted while 1 inner references still exist ...
これは局所変数 $obj がタイ変数のオブジェクトを参照しているため、オブジェ
クトのリファレンスカウントが 0 にならず、untie がオブジェクトを廃棄でき
ないことを表しています。この問題は、局所変数 $obj の有効範囲を限定するこ
とで解決することができます。
# 監視を止める(修正版)
sub unmonitor {
my ($pkg, $rvar) = @_;
my $last_value;
{
my $obj = tied $$rvar;
$last_value = $obj->{'value'};
$obj->{'name'} = '__UNMONITOR__'
}
untie $$rvar;
$$rvar = $last_value;
}
局所変数 $obj をブロック {} 内で定義します。こうすることで $obj の有効範
囲をブロック内に限定することができます。ブロックから抜けた時点で $obj は
無効となり、オブジェクトのリファレンスカウントが -1 されるため、untie で
オブジェクトを廃棄することができます。
また、untie される時にメソッド DESTORY が呼び出されますが、変数の監視
を止めたわけですから、DESTROY のメッセージを出力しない方が良いでしょう。
そこで、オブジェクトの name に __UNMONITOR__ をセットし、DESTROY でチェッ
クすることにします。
# 修正版
sub DESTROY {
my $obj = shift;
if( $obj->{'name'} ne '__UNMONITOR__' ){
print 'DESTORY $', "$obj->{'name'}\n";
}
}
プログラムの修正は簡単ですね。オブジェクトから name の値を取り出し、それ
を __UNMONITOR__ と比較するだけです。違っていれば、メッセージを出力しま
す。
それでは、untie を unmonitor に変更して、実行してみましょう。
$x = 10;
tie $x, 'MonScalar', 'x', $x;
$y = $x;
print "y = $y\n";
$x = 100;
$z = $x;
print "z = $z\n";
MonScalar->unmonitor( \$x );
print "x = $x\n";
実行結果
Read : $x -> 10
y = 10
Write : $x <- 100
Read : $x -> 100
z = 100
x = 100
監視を止めた後でも $x の値が 100 となります。正常に動作していますね。
○配列とタイ
次は配列です。配列にタイを適用する場合、個々の要素へのアクセスの他にも、
push, pop など関数を使ったアクセスがあります。ところが、現在のところ配列
のタイでサポートされている操作は、要素の読み書きのみです。まあ、近い将来
に改善されると思います。
配列の場合、tie が実行されると TIEARRAY が呼び出されます。そして、各要
素にアクセスするたびに、次のメソッドが呼び出されます。
アクション | 呼び出されるメソッド
------------+------------------------------------
読み出し | $obj->FETCH( $index );
書き込み | $obj->STORE( $index, $new_value );
廃棄 | $obj->DESTROY();
メソッドの名前はスカラーと同じですが、アクセスした要素の添字が渡されるこ
とに注意してください。
それでは簡単な実行例として、配列のアクセスを監視するプログラムを作って
みましょう。最初にパッケージ MonArray を定義します。
package MonArray;
sub TIEARRAY {
my ($pkg, $name, $ra) = @_;
my $obj = {
name => $name, array => [@$ra],
};
bless $obj, $pkg;
$obj;
}
sub FETCH {
my ($obj, $index) = @_;
my $value = $obj->{'array'}->[$index];
print 'Read : $',$obj->{'name'}, "[$index] -> $value\n";
$value;
}
sub STORE {
my ($obj, $index, $new_value) = @_;
print 'Write : $',$obj->{'name'}, "[$index] <- $new_value\n";
$obj->{'array'}->[$index] = $new_value;
}
sub DESTROY {
my $obj = shift;
if( $obj->{'name'} ne '__UNMONITOR__' ){
print 'DESTORY @', "$obj->{'name'}\n";
}
}
sub unmonitor {
my ($pkg, $rvar) = @_;
my $last_array;
{
my $obj = tied @$rvar;
$last_array = $obj->{'array'};
$obj->{'name'} = '__UNMONITOR__'
}
untie @$rvar;
@$rvar = @$last_array;
}
スカラーと同様に、TIEARRAY には配列名と配列そのものを渡します。配列を
渡す時はリファレンスを使った方が簡単です。次にオブジェクトを生成し、名前
を name に、配列は無名の配列にコピーして array にセットします。後はオブ
ジェクトをブレスして返すだけです。FETCH と STORE は簡単です。引数として
渡された添字で、オブジェクトに格納された無名の配列にアクセスすればいいわ
けです。監視を止める場合は、スカラーと同様に unmonitor を使います。オブ
ジェクトに格納されている配列を、監視していた配列に代入するだけです。
それでは実行してみましょう。
package main;
@a = (10, 20, 30);
tie @a, 'MonArray', 'a', \@a;
$x = $a[1];
print "x = $x\n";
$a[1] = 200;
$y = $a[1];
print "y = $y\n";
MonArray->unmonitor( \@a );
print "@a\n";
実行結果
Read : $a[1] -> 20
x = 20
Write : $a[1] <- 200
Read : $a[1] -> 200
y = 200
10 200 30
最初に tie を使って配列 @a とクラス MonArray を結び付けます。この後 @a
の要素にアクセスすると、MonArray のメソッド FETCH と STORE が呼び出され
ます。unmonitor で監視を止めた後でも、配列の値はきちんと更新されています
ね。
○ハッシュとタイ
配列の場合と異なり、タイによるハッシュへのアクセスは、個々の要素へのア
クセス、ハッシュ全体の操作、関数による操作の全てを完全にサポートしていま
す。
ハッシュの場合、tie が実行されると TIEHASH が呼び出されます。そして、
ハッシュにアクセスするたびに、次のメソッドが呼び出されます。
操作例 | 呼び出されるメソッド
-------------------+----------------------
$h{a}; | $obj->FETCH('a');
$h{a} = 1; | $obj->STORE('a', 1);
delete $h{a}; | $obj->DELETE('a');
exists $h{a}; | $obj->EXISTS('a');
%h = (); | $obj->CLEAR();
|
%h = ( a => 1 ); | $obj->CLEAR();
| $obj->STORE('a');
|
keys, values, each | $lk = $obj->FIRSTKEY();
| do {
| $val = $obj->FETCH($lk);
| } while ( $lk = $obj->NEXTKEY($lk) );
FIRSTKEY は最初のキーを、NEXTKEY は次のキーを返すように作る必要があり
ます。keys ではこの2つのメソッドが呼び出され、values や each では、各々
のキーについて FETCH が呼び出されます。
定義するメソッドが多くで面倒だと感じる方は、Tie::Hash, Tie::StdHash と
いう、タイハッシュに対する基本クラスを定義したモジュールが標準ライブラリ
に用意されているので、これを継承するといいでしょう。
それでは簡単な実行例として、ハッシュのアクセスを監視するプログラムを作っ
てみましょう。簡単な例題ということで、要素へのアクセスとハッシュ全体をク
リアする操作だけを監視することにします。最初にパッケージ MonHash を定義
します。
package MonHash;
sub TIEHASH {
my ($pkg, $name, $rh) = @_;
my $obj = {
name => $name, hash => {%$rh},
};
bless $obj, $pkg;
$obj;
}
sub FETCH {
my ($obj, $index) = @_;
my $value = $obj->{'hash'}->{$index};
print 'Read : $', $obj->{'name'}, "{$index} -> $value\n";
$value;
}
sub STORE {
my ($obj, $index, $new_value) = @_;
print 'Write : $', $obj->{'name'}, "{$index} <- $new_value\n";
$obj->{'hash'}->{$index} = $new_value;
}
sub CLEAR {
my $obj = shift;
print 'Clear : %', "$obj->{'name'}\n";
$obj->{'hash'} = {};
}
sub DESTROY {
my $obj = shift;
if( $obj->{'name'} ne '__UNMONITOR__' ){
print 'DESTORY %', "$obj->{'name'}\n";
}
}
sub unmonitor {
my ($pkg, $rvar) = @_;
my $last_hash;
{
my $obj = tied %$rvar;
$last_hash = $obj->{'hash'};
$obj->{'name'} = '__UNMONITOR__'
}
untie %$rvar;
%$rvar = %$last_hash;
}
スカラーや配列と同様に、TIEHASH にはハッシュ名とハッシュそのものを渡し
ます。ハッシュを渡す時はリファレンスを使います。次にオブジェクトを生成し、
名前を name に、ハッシュの値は無名のハッシュにコピーして hash にセットし
ます。後はオブジェクトをブレスして返すだけです。FETCH と STORE は簡単で
す。引数として渡された添字で、オブジェクトに格納された無名のハッシュにア
クセスすればいいわけです。CLEAR はもっと簡単ですね。メッセージを出力した
ら、ハッシュを空にするだけです。unmonitor も配列をハッシュに変えただけで
す。
それでは実行してみましょう。
package main;
%h = (a => 10, b => 20, c => 30);
print %h, "\n";
tie %h, 'MonHash', 'h', \%h;
$x = $h{'b'};
print "x = $x\n";
$h{'b'} = 200;
$y = $h{'b'};
print "y = $y\n";
%h = ( a => 100, b => 200, c => 300 );
MonHash->unmonitor( \%h );
print %h;
実行結果
a10b20c30
Read : $h{b} -> 20
x = 20
Write : $h{b} <- 200
Read : $h{b} -> 200
y = 200
Clear : %h
Write : $h{a} <- 100
Write : $h{b} <- 200
Write : $h{c} <- 300
a100b200c300
もう説明しなくてもいいですね。正常に動作しています。
○パッケージ Monitor の作成
ここまで、スカラー、配列、ハッシュの監視プログラムを作りましたが、いち
いちクラスを指定して tie 関数を呼び出すのは面倒ですね。そこで、与えられ
た変数の種類を調べて、適切な tie 関数を呼び出すプログラムを作りましょう。
作成するプログラムは、変数を監視する monitor と監視を止める unmonitor の
2つです。monitor は変数のリファレンスと名前を、unmonitor は変数のリファ
レンスを引数として受け取ります。
# 使用例
$x = 100;
monitor( \$x, 'x' );
unmonitor( \$x );
monitor と unmonitor はパッケージ Monitor に定義します。プログラムは次
のようになります。
# パッケージの定義(Monitor.pm)
package Monitor;
use Exporter;
@ISA = (Exporter);
@EXPORT_OK = ('monitor', 'unmonitor');
# 変数を監視する
sub monitor {
my ($rvar, $name) = @_;
my $type = ref( $rvar ); # 型のチェック
if( $type eq 'SCALAR' ){
tie $$rvar, 'MonScalar', $name, $$rvar;
} elsif( $type eq 'ARRAY' ){
tie @$rvar, 'MonArray', $name, $rvar;
} elsif( $type eq 'HASH' ){
tie %$rvar = 'MonHash', $name, $rvar;
} else {
print STDERR "リファレンスが必要です\n";
}
}
# 変数の監視を止める
sub unmonitor {
my $rvar = shift;
my $type = ref( $rvar ); # 型のチェック
if( $type eq 'SCALAR' ){
MonScalar->unmonitor( $rvar );
} elsif( $type eq 'ARRAY' ){
MonArray->unmonitor( $rvar );
} elsif( $type eq 'HASH' ){
MonHash->unmonitor( $rvar );
} else {
print STDERR "リファレンスが必要です\n";
}
}
2つのプログラムともに、関数 ref を呼び出してリファレンス先のデータの種
類を調べ、適切な関数を呼び出すだけです。MonScalar, MonArray, MonHash は
ファイル Monitor.pm にまとめて定義しておきます。
それでは実行例を示します。
# テストプログラム(montest.pl)
use Monitor ('monitor', 'unmonitor');
$x = 10;
@a = (100, 200, 300);
%h = (a => 1, b => 2, c => 3 );
monitor( \$x, 'x' );
monitor( \@a, 'a' );
monitor( \%h, 'h' );
$x = 20;
$y1 = $x;
print "y1 = $y1\n";
$a[2] = 3000;
$y2 = $a[2];
print "y2 = $y2\n";
$h{'c'} = 30;
$y3 = $h{'c'};
print "y3 = $y3\n";
unmonitor( \$x );
unmonitor( \@a );
unmonitor( \%h );
print "x = $x\n";
print "a = @a\n";
print "h = ", %h, "\n";
実行結果
Write : $x <- 20
Read : $x -> 20
y1 = 20
Write : $a[2] <- 3000
Read : $a[2] -> 3000
y2 = 3000
Write : $h{c} <- 30
Read : $h{c} -> 30
y3 = 30
x = 20
a = 100 200 3000
h = a1b2c30
正常に動作していますね。
タイの最も有効な利用方法は、ハッシュとデータベースを結び付けることです
が、本講座の範囲を超えるので説明を割愛いたします。また、ファイルハンドル
とタイの説明も割愛させていただきます。興味のある方は参考文献を読んでくだ
さい。
○おわりに
最近、インタプリタ形式のプログラミング言語(スクリプト言語ともいう)が
注目を集めています。その中で、Perl は CGI スクリプトを書くためのプログラ
ミング言語として、その普及度は目を見張るものがあります。この講座では、筆
者のスキル不足のため、CGI プログラミングを取り上げることはできませんでし
た。しかしながら、Perl の基本からオブジェクト指向まで、プログラミング言
語としての機能は一通り説明できたと思っています。
Perl は応用範囲の広いプログラミング言語です。CGI やネットワークの他に
も、GUI ツールキット Tk を利用するためのモジュール Tk.pm [*2] をロードす
ることで、Perl でも GUI アプリケーションを作成することができます。これか
らも、いろいろな分野で Perl が利用されることでしょう。皆さんも Perl を使っ
てプログラミングを楽しんでください。
最後になりましたが、この講座が少しでも皆様のお役に立てれば、筆者として
はこれほどの幸せはありません。長い間お付き合いいただいた読者の皆様、なら
びに編集スタッフの方々に感謝の意を表します。
[*2] Perl と Tk.pm の組み合わせを「Perl/Tk」といいます。なお、
Perl/Tk を実行するために Tcl/Tk は必要ありません。use Tk; で
Tk の利用が可能になります。
Perl のモジュールは総合 Perl アーカイブネットワーク (CPAN)
からダウンロードすることができます。ActivePerl 用 Tk.pm の入手
先は、須栗歩人氏のホームページ
http://members.xoom.com/tcltk/index.html
を参照してください。Tcl/Tk や Perl/Tk の情報がとても参考になり
ます。
―参考文献―
[1] Larry Wall, Tom Christiansen, Randal L. Schwartz 共著「プログラミン
グPerl」改訂版 オライリー・ジャパン 1997
[2] Sriram Srinivasan 著「実用Perlプログラミング」オライリー・ジャ
パン 1998
(EOF)